#include <iostream>
#include <iomanip>
#include <fstream>

#include "..\..\low-level\memory.h"
#include "Triple__test.h"
#include "..\..\low-level\Triple.h"

#define REPEAT(N) for (int i = 0; i < (N); i++)

static Triple *dest, *a, *b;

//----------------------------------------------------------------

void Triple__Add_Triple_____Triple_init()
{
	dest->Reset();
	a->Set(1.0f, 2.0f, 3.0f);
	b->Set(4.0f, 5.0f, 6.0f);
}

void Triple__Add_Triple_____Triple_exec()
{
	(*dest) = (*a) + (*b);
}

//----------------------------------------------------------------

void Triple__Add_Triple_init()
{
	dest->Reset();
	a->Set(1.0f, 2.0f, 3.0f);
}

void Triple__Add_Triple_exec()
{
	(*dest) += (*a);
}

//----------------------------------------------------------------

void Triple__Multiply_Triple_____Triple_init()
{
	dest->Reset();
	a->Set(1.0f, 2.0f, 3.0f);
	b->Set(4.0f, 5.0f, 6.0f);
}

void Triple__Multiply_Triple_____Triple_exec()
{
	(*dest) = (*a) * (*b);
}

//----------------------------------------------------------------

void Triple__Integrate_Triple_init()
{
	dest->Reset();
	a->Set(1.0f, 2.0f, 3.0f);
}

void Triple__Integrate_Triple_exec()
{
	dest->Integrate(*a, 0.1f);
}

//----------------------------------------------------------------

void Triple__DotProduct_Triple_init()
{
	dest->Reset();
	a->Set(1.0f, 2.0f, 3.0f);
}

void Triple__DotProduct_Triple_exec()
{
	dest->DotProduct(*a);
}

//----------------------------------------------------------------

void Triple__SquareDistance_Triple_____Triple_init()
{
	dest->Reset();
	a->Set(1.0f, 2.0f, 3.0f);
	a->Set(4.0f, 5.0f, 6.0f);
}

void Triple__SquareDistance_Triple_____Triple_exec()
{
	dest->SquareDistance(*a, *b);
}

//----------------------------------------------------------------

#define FARRAY_LENGTH 6

static char *farray_description[FARRAY_LENGTH] =
{
	"operator+",
	"Triple::Add(Triple *a)",
	"operator*",
	"Triple::Integrate(Triple *derivative, const float dt)",
	"Triple::DotProduct(Triple *a)",
	"Triple::SquareDistance(Triple *a, Triple *b)"
};

static void (*farray_init[FARRAY_LENGTH])() =
{
	Triple__Add_Triple_____Triple_init,
	Triple__Add_Triple_init,
	Triple__Multiply_Triple_____Triple_init,
	Triple__Integrate_Triple_init,
	Triple__DotProduct_Triple_init,
	Triple__SquareDistance_Triple_____Triple_init
};

static void (*farray_exec[FARRAY_LENGTH])() =
{
	Triple__Add_Triple_____Triple_exec,
	Triple__Add_Triple_exec,
	Triple__Multiply_Triple_____Triple_exec,
	Triple__Integrate_Triple_exec,
	Triple__DotProduct_Triple_exec,
	Triple__SquareDistance_Triple_____Triple_exec
};

static unsigned long long exec_time[FARRAY_LENGTH];

// only min number of cycles is registered
static void test(int index)
{
	unsigned long long min = 0;

	REPEAT(1 << 16)
	{
		farray_init[index]();

		const unsigned long long ts = rdtsc();
		farray_exec[index]();

		const unsigned long long diff = rdtsc() - ts;
		if (0 == min)
		{
			min = diff;
		}
		else
		{
			if (diff < min)
			{
				min  = diff;
			}
		}
	}
	exec_time[index] = min;
}

void Triple__test()
{
	ofstream file("Triple__test.m");

#ifdef SSSE3
	file << "% SSSE3 code" << endl;
#else
	file << "% standard code" << endl;
#endif

	dest = new Triple();
	a = new Triple();
	b = new Triple();

	for (int i = 0; i < FARRAY_LENGTH; i++)
	{
		test(i);
	}

	file << "description = [";
	for (int i = 0; i < FARRAY_LENGTH; i++)
	{
		file << '\''<< farray_description[i] << '\'';
		if (i < (FARRAY_LENGTH - 1))
		{
			file << ", ";
		}
	}
	file << "];" << endl;

	file << "exec_time = [";
	for (int i = 0; i < FARRAY_LENGTH; i++)
	{
		file << exec_time[i];
		if (i < (FARRAY_LENGTH - 1))
		{
			file << ", ";
		}
	}
	file << "];" << endl;

	file.close();
}
